options(repos = c(CRAN = "https://cloud.r-project.org"))
install.packages("httr")
##
## The downloaded binary packages are in
## /var/folders/dl/3s84k90j63n1y8ch80xh_0gc0000gn/T//RtmpE4qRQX/downloaded_packages
install.packages("jsonlite")
##
## The downloaded binary packages are in
## /var/folders/dl/3s84k90j63n1y8ch80xh_0gc0000gn/T//RtmpE4qRQX/downloaded_packages
library(httr)
library(jsonlite)
# Call the API
response <- GET("https://api.alternative.me/fng/?limit=1000")
# Parse JSON content
content_json <- content(response, as = "text", encoding = "UTF-8")
fng_data <- fromJSON(content_json)
# Convert the 'data' field to a data frame
fng_df <- fng_data$data
head(fng_df)
## value value_classification timestamp time_until_update
## 1 39 Fear 1744243200 2788
## 2 18 Extreme Fear 1744156800 <NA>
## 3 24 Extreme Fear 1744070400 <NA>
## 4 23 Extreme Fear 1743984000 <NA>
## 5 34 Fear 1743897600 <NA>
## 6 30 Fear 1743811200 <NA>
fng_df$date <- as.POSIXct(as.numeric(fng_df$timestamp), origin = "1970-01-01", tz = "UTC")
fng_df$value <- as.numeric(fng_df$value)
fng_final <- fng_df[, c("date", "value")]
names(fng_final) <- c("time", "FG") # Rename for consistency
head(fng_final)
## time FG
## 1 2025-04-10 39
## 2 2025-04-09 18
## 3 2025-04-08 24
## 4 2025-04-07 23
## 5 2025-04-06 34
## 6 2025-04-05 30
library(ggplot2)
ggplot(fng_final, aes(x = time, y = FG)) +
geom_line(color = "steelblue") +
labs(
title = "CNN Fear & Greed Index Over Time",
x = "Date",
y = "Fear & Greed Index"
) +
theme_minimal()
nrow(fng_final)
## [1] 1000
# Define the start and end dates as POSIXct objects
start_date <- as.POSIXct("2024-04-06", tz = "UTC")
end_date <- as.POSIXct("2025-04-06", tz = "UTC")
# Subset the data
fng_subset <- fng_final[fng_final$time >= start_date & fng_final$time <= end_date, ]
# Check the first few rows of the subset
head(fng_subset)
## time FG
## 5 2025-04-06 34
## 6 2025-04-05 30
## 7 2025-04-04 28
## 8 2025-04-03 25
## 9 2025-04-02 44
## 10 2025-04-01 34
# Plot the subsetted data
library(ggplot2)
ggplot(fng_subset, aes(x = time, y = FG)) +
geom_line(color = "steelblue") +
labs(
title = "CNN Fear & Greed Index (2024-04-06 to 2025-04-06)",
x = "Date",
y = "Fear & Greed Index"
) +
theme_minimal()
After pre-processing, we have “merged_df” which contains date, FG index and bitcoin daily close prices ranging from 04/06/2024 to 04/05/2025.
# Read the bitcoin dataset (ensure strings are not converted to factors)
bitcoin_ts <- read.csv('bitcoin_2024-04-06_2025-04-06.csv', stringsAsFactors = FALSE)
# Convert the 'Start' column to a POSIXct date object
bitcoin_ts$Start <- as.POSIXct(bitcoin_ts$Start, format = "%Y-%m-%d", tz = "UTC")
# Merge the F&G index dataset (fng_final) with the bitcoin dataset on matching dates
merged_df <- merge(fng_final, bitcoin_ts, by.x = "time", by.y = "Start")
# Select only the required columns and rename them:
# "date" for the matching date, "fg_index" for the Fear & Greed Index, and "bitcoin_close" for the Bitcoin closing price.
merged_df <- merged_df[, c("time", "FG", "Close")]
names(merged_df) <- c("date", "fg_index", "bitcoin_close")
# Check the first few rows of the merged data
head(merged_df)
## date fg_index bitcoin_close
## 1 2024-04-06 75 68904.86
## 2 2024-04-07 78 69388.74
## 3 2024-04-08 76 71631.77
## 4 2024-04-09 80 69198.22
## 5 2024-04-10 78 70513.60
## 6 2024-04-11 76 70112.05
# First plot the FG Index with its y-axis (left)
plot(merged_df$date, merged_df$fg_index, type = "l", col = "blue",
xlab = "Date", ylab = "FG Index", main = "FG Index and Bitcoin Price Over Time")
# Overlay the Bitcoin closing price with a new plot
par(new = TRUE)
plot(merged_df$date, merged_df$bitcoin_close, type = "l", col = "red",
axes = FALSE, xlab = "", ylab = "")
# Add a right-side y-axis for Bitcoin
axis(side = 4, col = "red", col.axis = "red")
mtext("Bitcoin Close", side = 4, line = 3, col = "red")
# Add a legend
legend("topleft", legend = c("FG Index", "Bitcoin Close"),
col = c("blue", "red"), lty = 1)
set.seed(1)
# Assuming 'merged_df' is already available from your earlier code
# with columns: date, fg_index, bitcoin_close
# For consistency with your ETH example, rename columns to "Date" and "Price"
bitcoin <- merged_df
names(bitcoin) <- c("Date", "FG", "Price")
# Convert the Date column to Date type (assuming it's in "YYYY-MM-DD" format)
bitcoin$Date <- as.Date(bitcoin$Date)
# Order the data by Date (if not already sorted)
bitcoin <- bitcoin[order(bitcoin$Date), ]
head(bitcoin)
## Date FG Price
## 1 2024-04-06 75 68904.86
## 2 2024-04-07 78 69388.74
## 3 2024-04-08 76 71631.77
## 4 2024-04-09 80 69198.22
## 5 2024-04-10 78 70513.60
## 6 2024-04-11 76 70112.05
#
library(zoo)
##
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
##
## as.Date, as.Date.numeric
# Convert Date to numeric for LOESS smoothing
bitcoin_num <- as.numeric(bitcoin$Date)
# Build each component as a zoo object
B_btc <- zoo(bitcoin$Price, bitcoin$Date)
B_trend <- zoo(loess(bitcoin$Price ~ bitcoin_num, span = 0.6)$fitted, bitcoin$Date)
B_noise <- zoo(bitcoin$Price - loess(bitcoin$Price ~ bitcoin_num, span = 0.07)$fitted, bitcoin$Date)
B_cycles <- B_btc - B_trend - B_noise
# Set up 4-row layout for plots
par(mfrow = c(4, 1), mar = c(3, 4, 2, 2), oma = c(1, 1, 5, 1)) # Adjust plot margins
# 1. Original Bitcoin Price
plot(B_btc, main = "Bitcoin Price (Original)", xlab = "Date", ylab = "Price", col = "black")
# 2. Trend component
plot(B_trend, main = "Trend (LOESS, span = 0.6)", xlab = "Date", ylab = "Trend", col = "red")
# 3. Noise component
plot(B_noise, main = "Noise (LOESS Residual, span = 0.07)", xlab = "Date", ylab = "Noise", col = "blue")
# 4. Cyclical component
plot(B_cycles, main = "Cyclical Component", xlab = "Date", ylab = "Cycles", col = "green")
mtext("Bitcoin Price Decomposition: Trend, Noise, Cycles", outer = TRUE, cex = 1.5, font = 2)
FG <- bitcoin$FG # FG index from merged_df
FG_date <- bitcoin$Date
# Convert Date to numeric for LOESS smoothing
fg_num <- as.numeric(FG_date)
# Build zoo objects
FG_index <- zoo(FG, FG_date)
FG_trend <- zoo(loess(FG ~ fg_num, span = 0.6)$fitted, FG_date)
FG_noise <- zoo(FG - loess(FG ~ fg_num, span = 0.07)$fitted, FG_date)
FG_cycles <- FG_index - FG_trend - FG_noise
# Plot layout: 4 stacked subplots + outer title
par(mfrow = c(4, 1), mar = c(3, 4, 2, 2), oma = c(1, 1, 5, 1))
plot(FG_index, main = "FG Index (Original)", xlab = "Date", ylab = "Index", col = "black")
plot(FG_trend, main = "Trend (LOESS, span = 0.6)", xlab = "Date", ylab = "Trend", col = "red")
plot(FG_noise, main = "Noise (LOESS Residual, span = 0.07)", xlab = "Date", ylab = "Noise", col = "blue")
plot(FG_cycles, main = "Cyclical Component", xlab = "Date", ylab = "Cycles", col = "green")
# Add overall title
mtext("Fear & Greed Index Decomposition: Trend, Noise, Cycles",
outer = TRUE, cex = 1.5, font = 2, line = 2)
# Bitcoin Price (from merged_df / bitcoin$Price)
B_ind <- bitcoin$Price
spectrum(B_ind,
spans = c(20, 20),
xlab = "Frequency - Cycles per Day",
main = "Bitcoin Daily Price - Smoothed Periodogram")
# FG Index (from merged_df / bitcoin$FG)
FG_ind <- bitcoin$FG
spectrum(FG_ind,
spans = c(20, 20),
xlab = "Frequency - Cycles per Day",
main = "Fear & Greed Index - Smoothed Periodogram")
# Compute the continuously compounded log returns and demean them
log_returns <- diff(log(bitcoin$Price))
logd <- log_returns - mean(log_returns)
# Plot the Bitcoin Price over time
par(mai=c(0.8,0.8,0.1,0.1))
plot(bitcoin$Price ~ bitcoin$Date, type="l",
xlab="Years", ylab="Bitcoin ($)",
main="Bitcoin Price Over Time")
# Plot the logarithmic transformation of Bitcoin Price
plot(log(bitcoin$Price) ~ bitcoin$Date, type="l",
xlab="Years", ylab="Log(Bitcoin Price)",
main="Log-Transformed Bitcoin Price")
# Plot the demeaned log returns of Bitcoin
# (Note: diff reduces the length by one, so use Date[-1])
plot(bitcoin$Date[-1], logd, type="l",
xlab="Years", ylab="Demeaned Log Return",
main="Demeaned Log Returns of Bitcoin")
# Recalculate if needed
bitcoin$log_price <- log(bitcoin$Price)
log_returns <- diff(bitcoin$log_price)
log_returns_demeaned <- log_returns - mean(log_returns)
# ACF 1: Original price
acf(bitcoin$Price, lag.max = 50, main = "ACF: Original Price")
# ACF 2: Log(price)
acf(bitcoin$log_price, lag.max = 50, main = "ACF: Log(Price)")
# ACF 3: Differenced log(price)
acf(log_returns, lag.max = 50, main = "ACF: Diff Log(Price)")
# ACF 4: Demeaned log returns
acf(log_returns_demeaned, lag.max = 50, main = "ACF: Demeaned Log Returns")
# Shared title
mtext("Figure X. ACF of Bitcoin Price: Original, Log, Differenced, Demeaned",
outer = TRUE, cex = 1.2, font = 2, line = 1)
# Prepare FG Index (from merged_df / bitcoin$FG)
FG_index <- bitcoin$FG
FG_date <- bitcoin$Date
# Log transform
log_FG <- log(FG_index)
# First difference of log
diff_log_FG <- diff(log_FG)
acf(FG_index, lag.max = 50, main = "ACF of Fear & Greed Index (Original)")
acf(log_FG, lag.max = 50, main = "ACF of Log(Fear & Greed Index)")
acf(diff_log_FG, lag.max = 50, main = "ACF of Differenced Log(FG Index)")
library(pomp)
##
## Attaching package: 'pomp'
## The following object is masked from 'package:zoo':
##
## time<-
# Precompute differenced log returns
bitcoin$log_price <- log(bitcoin$Price)
log_returns <- diff(bitcoin$log_price)
btc <- bitcoin[-1, ] # Remove first row (NA from diff)
btc$log_return <- log_returns
btc$time <- 1:nrow(btc)
btc_statenames <- c("V")
btc_paramnames <- c("sigma_omega", "phi", "theta", "V_0")
btc_rproc <- "
double omega;
omega = rnorm(0, sigma_omega);
V = theta * (1 - phi) + phi * sqrt(V) + sqrt(V) * omega;
if (V < 0) V = 0;
"
btc_rinit <- "V = V_0;"
btc_rmeasure <- "y = rnorm(0, sqrt(V));"
btc_dmeasure <- "lik = dnorm(y, 0, sqrt(V), give_log);"
btc_partrans <- parameter_trans(
log = c("sigma_omega", "theta", "V_0"),
logit = "phi"
)
btc_params <- c(
sigma_omega = 0.001,
phi = 0.2,
theta = 0.0004,
V_0 = 0.002
)
btc.pomp <- pomp(
data = data.frame(time = btc$time, y = btc$log_return),
times = "time",
t0 = 0,
statenames = btc_statenames,
paramnames = btc_paramnames,
rprocess = discrete_time(step.fun = Csnippet(btc_rproc), delta.t = 1),
rinit = Csnippet(btc_rinit),
rmeasure = Csnippet(btc_rmeasure),
dmeasure = Csnippet(btc_dmeasure),
params = btc_params,
partrans = btc_partrans
)
btc_sim <- simulate(btc.pomp, seed = 1)
plot(btc_sim, main = "Simulated vs. Observed Log Returns (Bitcoin)")
library(doParallel)
## Loading required package: foreach
## Loading required package: iterators
## Loading required package: parallel
library(doRNG)
## Loading required package: rngtools
registerDoParallel(detectCores())
registerDoRNG(1234)
# Tuning
run_level <- 2
Np <- switch(run_level, 100, 1000, 2000)
Nmif <- switch(run_level, 10, 100, 200)
Nreps_eval <- switch(run_level, 4, 10, 20)
Nreps_local <- switch(run_level, 5, 10, 20)
btc_rw_sd <- rw_sd(
sigma_omega = 0.01,
phi = 0.01,
theta = 0.01,
V_0 = ivp(0.01)
)
btc_mif <- foreach(i = 1:Nreps_local, .packages = 'pomp', .combine = c) %dopar% {
mif2(btc.pomp,
params = btc_params,
Np = Np,
Nmif = Nmif,
cooling.fraction.50 = 0.5,
rw.sd = btc_rw_sd)
}
btc_L <- foreach(i = 1:Nreps_local, .packages = 'pomp', .combine = rbind) %dopar% {
logmeanexp(replicate(Nreps_eval, logLik(pfilter(btc.pomp,
params = coef(btc_mif[[i]]),
Np = Np))),
se = TRUE)
}
btc_results <- data.frame(
logLik = btc_L[, 1],
logLik_se = btc_L[, 2],
t(sapply(btc_mif, coef))
)
summary(btc_results$logLik)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 818.1 818.4 818.5 818.5 818.6 818.6
plot(btc_mif)
pairs(~logLik + sigma_omega + theta + phi, data = btc_results)
btc_box <- rbind(
V_0 = c(0.0001, 0.02),
sigma_omega = c(0.0001, 0.1),
phi = c(0.01, 0.99),
theta = c(0.0001, 0.02)
)
Nreps_global <- switch(run_level, 10, 20, 100)
btc_global_mif <- foreach(i = 1:Nreps_global, .packages = 'pomp', .combine = c) %dopar% {
start_params <- apply(btc_box, 1, function(x) runif(1, min = x[1], max = x[2]))
mif2(
btc_mif[[1]], # use the model structure from local search
params = start_params,
Np = Np,
Nmif = Nmif,
cooling.fraction.50 = 0.5,
rw.sd = btc_rw_sd
)
}
btc_L_global <- foreach(i = 1:Nreps_global, .packages = 'pomp', .combine = rbind) %dopar% {
logmeanexp(
replicate(Nreps_eval, logLik(pfilter(
btc.pomp,
params = coef(btc_global_mif[[i]]),
Np = Np
))),
se = TRUE
)
}
btc_results_global <- data.frame(
logLik = btc_L_global[, 1],
logLik_se = btc_L_global[, 2],
t(sapply(btc_global_mif, coef))
)
summary(btc_results_global$logLik)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 817.8 818.1 818.2 818.2 818.4 818.6
# Save results if desired
write.csv(btc_results_global, "btc_global_params.csv", row.names = FALSE)
# Diagnostic plots
plot(btc_global_mif)
pairs(~logLik + sigma_omega + theta + phi, data = btc_results_global)